<template>
  <div class="light-cube">
    <h1>平行光立方体</h1>
    <p>这个示例展示了WebGL中平行光照效果，包括环境光和方向光照。通过调整光照参数，可以观察不同光照条件下立方体的渲染效果。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>光照效果演示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="400" height="300"></canvas>
        <div class="control-panel">
          <el-form :model="lightForm" label-width="120px">
            <el-form-item label="环境光强度">
              <el-slider
                v-model="lightForm.ambientIntensity"
                :min="0"
                :max="1"
                :step="0.1"
                @input="updateLight"
              />
            </el-form-item>
            <el-form-item label="方向光强度">
              <el-slider
                v-model="lightForm.diffuseIntensity"
                :min="0"
                :max="1"
                :step="0.1"
                @input="updateLight"
              />
            </el-form-item>
            <el-form-item label="光照方向 X">
              <el-slider
                v-model="lightForm.lightDirX"
                :min="-1"
                :max="1"
                :step="0.1"
                @input="updateLight"
              />
            </el-form-item>
            <el-form-item label="光照方向 Y">
              <el-slider
                v-model="lightForm.lightDirY"
                :min="-1"
                :max="1"
                :step="0.1"
                @input="updateLight"
              />
            </el-form-item>
            <el-form-item label="光照方向 Z">
              <el-slider
                v-model="lightForm.lightDirZ"
                :min="-1"
                :max="1"
                :step="0.1"
                @input="updateLight"
              />
            </el-form-item>
          </el-form>
          <div class="light-info">当前光照方向: ({{ lightForm.lightDirX }}, {{ lightForm.lightDirY }}, {{ lightForm.lightDirZ }})</div>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>示例代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code>
// 顶点着色器
{{ VSHADER_SOURCE }}

// 片元着色器
{{ FSHADER_SOURCE }}
        </code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive, onUnmounted } from 'vue'
import { WebGLUtils } from '@/utils/webgl'
import { mat4, vec3 } from 'gl-matrix'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null
let webglUtils: WebGLUtils | null = null

const lightForm = reactive({
  ambientIntensity: 0.2,
  diffuseIntensity: 0.8,
  lightDirX: 0.5,
  lightDirY: 3.0,
  lightDirZ: 4.0
})

// 顶点着色器
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  attribute vec4 a_Normal;
  uniform mat4 u_MvpMatrix;
  uniform mat4 u_ModelMatrix;
  uniform mat4 u_NormalMatrix;
  varying vec3 v_Normal;
  void main() {
    gl_Position = u_MvpMatrix * a_Position;
    v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));
  }
`

// 片元着色器
const FSHADER_SOURCE = `
  precision mediump float;
  uniform vec3 u_LightDirection;
  uniform vec3 u_AmbientLight;
  uniform vec3 u_LightColor;
  varying vec3 v_Normal;
  void main() {
    vec3 normal = normalize(v_Normal);
    float nDotL = max(dot(normalize(u_LightDirection), normal), 0.0);
    vec3 diffuse = u_LightColor * nDotL;
    vec3 ambient = u_AmbientLight;
    vec3 baseColor = vec3(0.8, 0.0, 0.0);
    gl_FragColor = vec4((diffuse + ambient) * baseColor, 1.0);
  }
`

function initVertexBuffers(gl: WebGLRenderingContext): number {
  const vertices = new Float32Array([
    1.0, 1.0, 1.0,  -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,   1.0,-1.0, 1.0,
    1.0, 1.0,-1.0,  -1.0, 1.0,-1.0,  -1.0,-1.0,-1.0,   1.0,-1.0,-1.0,
    1.0, 1.0, 1.0,   1.0, 1.0,-1.0,  -1.0, 1.0,-1.0,  -1.0, 1.0, 1.0,
    1.0,-1.0, 1.0,   1.0,-1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0,-1.0, 1.0,
    1.0, 1.0, 1.0,   1.0,-1.0, 1.0,   1.0,-1.0,-1.0,   1.0, 1.0,-1.0,
   -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,  -1.0,-1.0,-1.0,  -1.0, 1.0,-1.0
  ])

  const normals = new Float32Array([
    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,
    0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0,
    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,
    0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,
    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,
   -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0
  ])

  const indices = new Uint8Array([
    0, 1, 2,   0, 2, 3,
    4, 5, 6,   4, 6, 7,
    8, 9,10,   8,10,11,
    12,13,14,  12,14,15,
    16,17,18,  16,18,19,
    20,21,22,  20,22,23
  ])

  const indexBuffer = gl.createBuffer()
  const vertexBuffer = gl.createBuffer()
  const normalBuffer = gl.createBuffer()

  if (!indexBuffer || !vertexBuffer || !normalBuffer) {
    console.error('Failed to create buffers')
    return 0
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
  const a_Position = gl.getAttribLocation(program!, 'a_Position')
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0)
  gl.enableVertexAttribArray(a_Position)

  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW)
  const a_Normal = gl.getAttribLocation(program!, 'a_Normal')
  gl.vertexAttribPointer(a_Normal, 3, gl.FLOAT, false, 0, 0)
  gl.enableVertexAttribArray(a_Normal)

  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)

  return indices.length
}

function draw() {
  if (!gl || !program) return

  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.enable(gl.DEPTH_TEST)
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

  updateMatrices()

  const u_LightDirection = gl.getUniformLocation(program, 'u_LightDirection')
  const u_AmbientLight = gl.getUniformLocation(program, 'u_AmbientLight')
  const u_LightColor = gl.getUniformLocation(program, 'u_LightColor')

  const lightDir = vec3.fromValues(lightForm.lightDirX, lightForm.lightDirY, lightForm.lightDirZ)
  vec3.normalize(lightDir, lightDir)

  gl.uniform3fv(u_LightDirection, lightDir)
  gl.uniform3f(u_AmbientLight, lightForm.ambientIntensity, lightForm.ambientIntensity, lightForm.ambientIntensity)
  gl.uniform3f(u_LightColor, lightForm.diffuseIntensity, lightForm.diffuseIntensity, lightForm.diffuseIntensity)

  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
}

let n: number
let angle = 0
let animationId: number

function initGL() {
  if (!demoCanvas.value) return

  gl = demoCanvas.value.getContext('webgl')
  if (!gl) {
    console.error('Failed to get WebGL context')
    return
  }

  webglUtils = new WebGLUtils(gl)

  const vertexShader = webglUtils.createShader(gl.VERTEX_SHADER, VSHADER_SOURCE)
  const fragmentShader = webglUtils.createShader(gl.FRAGMENT_SHADER, FSHADER_SOURCE)
  if (!vertexShader || !fragmentShader) {
    console.error('Failed to create shaders')
    return
  }

  program = webglUtils.createProgram(vertexShader, fragmentShader)
  if (!program) {
    console.error('Failed to create program')
    return
  }

  gl.useProgram(program)

  n = initVertexBuffers(gl)

  const u_LightDirection = gl.getUniformLocation(program, 'u_LightDirection')
  const u_AmbientLight = gl.getUniformLocation(program, 'u_AmbientLight')
  const u_LightColor = gl.getUniformLocation(program, 'u_LightColor')

  if (!u_LightDirection || !u_AmbientLight || !u_LightColor) {
    console.error('Failed to get uniform locations')
    return
  }

  const lightDir = vec3.fromValues(lightForm.lightDirX, lightForm.lightDirY, lightForm.lightDirZ)
  vec3.normalize(lightDir, lightDir)

  gl.uniform3fv(u_LightDirection, lightDir)
  gl.uniform3f(u_AmbientLight, lightForm.ambientIntensity, lightForm.ambientIntensity, lightForm.ambientIntensity)
  gl.uniform3f(u_LightColor, lightForm.diffuseIntensity, lightForm.diffuseIntensity, lightForm.diffuseIntensity)

  animate()
}

function animate() {
  angle = (angle + 1) % 360
  draw()
  animationId = requestAnimationFrame(animate)
}

onMounted(() => {
  initGL()
})

onUnmounted(() => {
  if (animationId) {
    cancelAnimationFrame(animationId)
  }
})

function updateMatrices() {
  if (!gl || !program) return

  const mvpMatrix = mat4.create()
  const modelMatrix = mat4.create()
  const normalMatrix = mat4.create()
  const projMatrix = mat4.create()
  const viewMatrix = mat4.create()

  mat4.identity(modelMatrix)
  mat4.rotate(modelMatrix, modelMatrix, angle * Math.PI / 180, [0.5, 1, 0.2])

  const fov = 45 * Math.PI / 180
  const aspect = gl.canvas.width / gl.canvas.height
  const near = 0.1
  const far = 100.0
  mat4.perspective(projMatrix, fov, aspect, near, far)

  mat4.lookAt(viewMatrix,
    [0, 0, 6],
    [0, 0, 0],
    [0, 1, 0]
  )

  mat4.multiply(mvpMatrix, projMatrix, viewMatrix)
  mat4.multiply(mvpMatrix, mvpMatrix, modelMatrix)

  mat4.invert(normalMatrix, modelMatrix)
  mat4.transpose(normalMatrix, normalMatrix)

  const u_MvpMatrix = gl.getUniformLocation(program, 'u_MvpMatrix')
  const u_ModelMatrix = gl.getUniformLocation(program, 'u_ModelMatrix')
  const u_NormalMatrix = gl.getUniformLocation(program, 'u_NormalMatrix')

  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix)
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix)
  gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix)
}

function updateLight() {
  draw()
}
</script>

<style scoped>
.light-cube {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

h1 {
  color: #303133;
  margin-bottom: 20px;
}

p {
  line-height: 1.6;
  color: #606266;
  margin-bottom: 20px;
}

.demo-section {
  margin-bottom: 20px;
}

.card-header {
  display: flex;
  align-items: center;
}

.card-header h3 {
  margin: 0;
  color: #409EFF;
}

.demo-content {
  display: flex;
  gap: 20px;
}

canvas {
  border: 1px solid #dcdfe6;
  background-color: #000;
}

.control-panel {
  flex: 1;
  max-width: 400px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.light-info {
  margin-top: 15px;
  color: #606266;
  font-size: 14px;
}

.code-section {
  margin-top: 20px;
}

.code-content {
  background-color: #f8f9fa;
  padding: 15px;
  border-radius: 4px;
}

code {
  font-family: monospace;
  color: #409EFF;
}

pre {
  margin: 0;
  white-space: pre-wrap;
}
</style>
